﻿using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OS.Http.Enums;
using OS.Http.Models;

namespace OS.Http.Extention
{
    /// <summary>
    /// Http客户端扩展类
    /// </summary>
    public static class HttpClientExtention
    {
        private const string LineBreak = "\r\n";

        private const string TempString = "abcdefghijklmnopqrstuvwxyz0123456789";

        public static Encoding Encoding { get; set; } = Encoding.UTF8;

        /// <summary>
        /// 执行请求方法
        /// </summary>
        /// <param name="client">Http客户端</param>
        /// <param name="request">Http请求</param>
        /// <returns></returns>
        public static Task<HttpResponseMessage> RestSend(this HttpClient client, BaseHttpRequest request)
        {
            return client.RestSend(request, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
        }

        /// <summary>
        /// 执行请求方法
        /// </summary>
        /// <param name="client">Http客户端</param>
        /// <param name="request">Http请求</param>
        /// <param name="completionOption">请求完成可选项</param>
        /// <returns></returns>
        public static Task<HttpResponseMessage> RestSend(this HttpClient client, BaseHttpRequest request,
            HttpCompletionOption completionOption)
        {
            return client.RestSend(request, completionOption, CancellationToken.None);
        }

        /// <summary>
        /// 执行请求方法
        /// </summary>
        /// <param name="client">Http客户端</param>
        /// <param name="request">Http请求</param>
        /// <param name="completionOption">请求完成可选项</param>
        /// <param name="cancellationToken">请求取消Token</param>
        /// <returns></returns>
        public static Task<HttpResponseMessage> RestSend(this HttpClient client, BaseHttpRequest request,
            HttpCompletionOption completionOption, CancellationToken cancellationToken)
        {
            var requestMessage = ConfigureRequestMessage(request);
            if (request.TimeOutMilliSeconds > 0)
            {
                client.Timeout = TimeSpan.FromMilliseconds(request.TimeOutMilliSeconds);
            }

            return client.SendAsync(requestMessage, completionOption, cancellationToken);
        }

        /// <summary>
        /// 配置请求
        /// </summary>
        /// <param name="request">Http请求</param>
        /// <returns></returns>
        public static HttpRequestMessage ConfigureRequestMessage(BaseHttpRequest request)
        {
            var obj = new HttpRequestMessage
            {
                RequestUri = (string.IsNullOrEmpty(request.AddressUrl) ? request.Uri : new Uri(request.AddressUrl)),
                Method = new HttpMethod(request.BaseHttpMethod.ToString())
            };
            ConfigureRequestContent(obj, request);
            return obj;
        }

        /// <summary>
        /// 配置使用Content
        /// </summary>
        /// <param name="requestMessage">Http请求消息</param>
        /// <param name="request">Http请求</param>
        private static void ConfigureRequestContent(HttpRequestMessage requestMessage, BaseHttpRequest request)
        {
            if (request.BaseHttpMethod == BaseHttpMethod.Get) request.RequestSet?.Invoke(requestMessage);
            else if (request.HasFile)
            {
                var boundary = GetBoundary();
                var memoryStream = new MemoryStream();
                WriteMultipartFormData(memoryStream, request, boundary);
                memoryStream.Seek(0L, SeekOrigin.Begin);
                requestMessage.Content = new StreamContent(memoryStream);
                request.RequestSet?.Invoke(requestMessage);
                requestMessage.Content.Headers.Remove("Content-Type");
                requestMessage.Content.Headers.TryAddWithoutValidation("Content-Type",
                    string.Format("multipart/form-data;boundary={0}", boundary));
            }
            else
            {
                var normalFormData = GetNormalFormData(request);
                requestMessage.Content = new StringContent(normalFormData);
                request.RequestSet?.Invoke(requestMessage);
                if (requestMessage.Content.Headers.ContentType == null)
                    requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
            }
        }

        /// <summary>
        /// 写入表单的内容值【非文件参数 + 文件头 + 文件参数（内部完成） + 请求结束符】
        /// </summary>
        /// <param name="memory">内存流</param>
        /// <param name="request">Http请求</param>
        /// <param name="boundary">分割界限符</param>
        private static void WriteMultipartFormData(Stream memory, BaseHttpRequest request, string boundary)
        {
            foreach (var formParameter in request.FormParameters)
            {
                WriteStringTo(memory, GetMultipartFormData(formParameter, boundary));
            }

            foreach (var fileParameter in request.FileParameters)
            {
                WriteStringTo(memory, GetMultipartFileHeader(fileParameter, boundary));
                fileParameter.Writer(memory);
                WriteStringTo(memory, LineBreak);
            }
            WriteStringTo(memory, GetMultipartFooter(boundary));
        }

        /// <summary>
        /// 写入表单的内容值（文件头）
        /// </summary>
        /// <param name="file">文件参数</param>
        /// <param name="boundary">请求分割界限</param>
        /// <returns></returns>
        private static string GetMultipartFileHeader(FileParameter file, string boundary)
        {
            var text = file.ContentType ?? "application/octet-stream";
            return string.Format(
                "--{0}{1}Content-Disposition: form-data; name=\"{2}\"; filename=\"{3}\"{4} Content-Type: {5}{6}{7}",
                boundary, LineBreak, file.Name, file.FileName, LineBreak, text, LineBreak, LineBreak);
        }

        /// <summary>
        /// 写入表单的内容值（非文件参数）
        /// </summary>
        /// <param name="parameter">表单参数</param>
        /// <param name="boundary">请求分割界限</param>
        /// <returns></returns>
        private static string GetMultipartFormData(FormParameter parameter, string boundary)
        {
            return string.Format("--{0}{1}Content-Disposition: form-data; name=\"{2}\"{3}{4}{5}{6}", boundary, LineBreak, parameter.Name, LineBreak, LineBreak, parameter.Value, LineBreak);
        }

        /// <summary>
        /// 写入表单的内容值（请求结束符）
        /// </summary>
        /// <param name="boundary"></param>
        /// <returns></returns>
        private static string GetMultipartFooter(string boundary)
        {
            return string.Format("--{0}--{1}", boundary, LineBreak);
        }

        /// <summary>
        /// 写入请求的内容信息（非文件上传请求）
        /// </summary>
        /// <param name="request">Http请求</param>
        /// <returns></returns>
        private static string GetNormalFormData(BaseHttpRequest request)
        {
            var stringBuilder = new StringBuilder();
            foreach (var formParameter in request.FormParameters)
            {
                if (stringBuilder.Length > 1) stringBuilder.Append("&");
                stringBuilder.AppendFormat(formParameter.ToString(), Array.Empty<object>());
            }

            if (string.IsNullOrEmpty(request.CustomBody)) return stringBuilder.ToString();
            if (stringBuilder.Length > 1) stringBuilder.Append("&");
            stringBuilder.Append(request.CustomBody);
            return stringBuilder.ToString();
        }

        /// <summary>
        /// 写入数据方法（将数据写入 webrequest）
        /// </summary>
        /// <param name="stream">文件/文本流</param>
        /// <param name="toWrite">需要写入的字符串</param>
        private static void WriteStringTo(Stream stream, string toWrite)
        {
            var bytes = Encoding.GetBytes(toWrite);
            stream.Write(bytes, 0, bytes.Length);
        }

        /// <summary>
        /// 创建请求分割界限
        /// </summary>
        /// <returns></returns>
        private static string GetBoundary()
        {
            var stringBuilder = new StringBuilder();
            var random = new Random();
            for (var i = 0; i < 10; i++)
            {
                var index = random.Next(TempString.Length);
                stringBuilder.Append(TempString[index]);
            }

            return string.Format("-------{0}", stringBuilder);
        }
    }
}